/**
 * lajax4wx
 * log + ajax 前端日志解决方案 for微信小程序
 * Author: Sky.Sun
 * Date: 2017/09/06
 */

/**
 * 使 Error 对象支持 JSON 序列化
 */
if (!('toJSON' in Error.prototype)) {
  /* eslint-disable no-extend-native */
  Object.defineProperty(Error.prototype, 'toJSON', {
    value() {
      const alt = {};
      Object.getOwnPropertyNames(this).forEach(function(key) {
        alt[key] = this[key];
      }, this);
      return alt;
    },
    configurable: true,
    writable: true
  });
  /* eslint-enable no-extend-native */
}
var checkTimeout = null;
var _EXTRADATA = null;
var lajaxObj = null;
class Lajax {
  /* eslint-disable no-console, no-bitwise*/
  constructor(param) {
    let config = param;
    if (typeof config === 'undefined') {
      throw new Error('lajax初始化错误 - 构造函数的参数不能为空！');
    }
    if (typeof config === 'object') {
      if (typeof param.url !== 'string') {
        throw new Error('lajax初始化错误 - 构造函数的参数 url 必须是一个字符串！');
      } else if (param.app == null) {
        throw new Error('lajax初始化错误 - 构造函数的参数 app 不能为空！');
      }
    } else {
      throw new Error('lajax初始化错误 - 构造函数的参数格式不正确！');
    }

    // 服务端 url 地址
    this.url = config.url;

    // 微信小程序实例，通过在 App() 函数内传入 this，或者调用 getApp() 获得
    this.app = config.app;

    this.connectInfo = config.connectInfo;

    //系统信息
    this.systemInfo = config.systemInfo;
    this.app.systemInfo = config.systemInfo;

    //小程序的版本号
    this.miniVer = config.miniVer;

    //是否在控制台输入日志,false不输出,true输出
    this.printLogFlg = config.printLogFlg;

    this.printBleLog = config.printBleLog; //是否输出蓝牙日志

    this.printAppLog = config.printAppLog; //是否输出APP日志，不包含蓝牙日志

    // 是否自动记录未捕获错误
    this.autoLogError = config.autoLogError == null ? true : config.autoLogError;

    // 是否要格式化 console 打印的内容
    this.stylize = config.stylize == null ? true : config.stylize;

    // 默认的间隔发送时间（毫秒）
    const defaultInterval = 6000;

    // 间隔发送时间
    this.interval = config.interval == null ? defaultInterval : config.interval;

    // 默认的最大请求出错次数
    const defaultMaxErrorReq = 10;

    // 发送请求出错的最大次数，超过此次数则不再发送请求，但依然会记录请求到队列中
    this.maxErrorReq = config.maxErrorReq == null ? defaultMaxErrorReq : config.maxErrorReq;

    // 当前请求出错次数
    this.errorReq = 0;

    // 日志队列
    this.queue = [];

    this.sort = 0;

    this.appSort = 0;

    this.bleSort = 0;

    this.conSort = 0;

    this.conRFID = null;

    // 发送日志请求的 request 对象
    this.requestTask = null;

    // 原始的 console 对象
    this.console = {
      log: console.log,
      info: console.info,
      warn: console.warn,
      error: console.error
    };

    // 初始化
    this._init();

    lajaxObj = this;
  }

  _setConRFID(rfid) {
    this.conRFID = rfid;
  }

  /**
   * 初始化方法
   * 
   * @memberof Lajax
   */
  _init() {

    // 重写 console
    this._overwriteConsole();

    // 获取唯一请求id
    this._getReqId();

    // 加载之前未发送的历史日志 不进行保存
    // this._loadFromStorage();

    // 自动记录异常
    this._exceptionHandler();

    // 绑定页面隐藏事件 不进行保存
    // this._storageUnsendData();

    // // // 定时发送日志请求 去除定时发送
    // this.timer = setInterval(() => {
    //   this._send();
    // }, this.interval);

    this._checkSendLog();

  }

  _checkSendLog() {
    if (this.app.globalData.connect_stat != 'fail') {
        return;
    }
    if (checkTimeout != null) {
      clearTimeout(checkTimeout);
      checkTimeout = null;
    }

    var that = this;

    if (that.queue.length > 0) {

      if (that.printLogFlg) {
        // that.console.log(`%c[${that._getTimeString(null)}] - 准备发送 ${that.queue.length} 条日志！`, `color: ${Lajax.colorEnum.sendSuccess}`);
      }

      that._send();
    }else {

      if (that.printLogFlg) {
        // that.console.log(`%c[${that._getTimeString(null)}] - 没有未发送的日志！`, `color: ${Lajax.colorEnum.sendSuccess}`);
      }

      if (checkTimeout != null) {
        clearTimeout(checkTimeout);
        checkTimeout = null;
      }
      
      checkTimeout = setTimeout(function() {
        that._checkSendLog();
      }, that.interval);
    }

  }

  /**
   * 获取或者生成唯一请求 id
   * 
   * @memberof Lajax
   */
  _getReqId() {
    const reqId = this.app.globalData.reqId;
    // if (!reqId) {
      // 生成一个 reqId https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript/8809472#8809472
      let time = Date.now();
      if (typeof performance !== 'undefined' && typeof performance.now === 'function') {
        // 使用更高精度的时间
        time += performance.now();
      }
      this.reqId = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, char => {
        const rand = (time + (Math.random() * 16)) % 16 | 0;
        time = Math.floor(time / 16);
        return (char === 'x' ? rand : ((rand & 0x3) | 0x8)).toString(16);
      });
      this.app.globalData.reqId = this.reqId;
      this.sort = 0;
      this.appSort = 0;
      this.bleSort = 0;
      this.conSort = 0;
    // }
  }

  /**
   * 从微信缓存加载之前未发送的历史日志
   * 
   * @memberof Lajax
   */
  _loadFromStorage() {
    let lastData = wx.getStorageSync('lajax');
    if (lastData) {
      lastData = JSON.parse(lastData);
      if (Array.isArray(lastData) && lastData.length) {
        this.lastUnsend = lastData.length;
        this.queue = lastData;
      }
      wx.removeStorageSync('lajax');
    }
  }

  /**
   * 自动记录异常
   * 
   * @memberof Lajax
   */
  _exceptionHandler() {
    // 页面未捕获异常
    if (this.autoLogError) {
      this.app.onError = err => {
        this.error('[OnError]', err.message, `(${err.lineno}行${err.colno}列)`);
      };
    }
  }

  /**
   * 解析 url
   * 
   * @param {string} url
   * @returns 
   * @memberof Lajax
   */
  _resolveUrl(url) {
    const link = document.createElement('a');
    link.href = url;
    return `${link.protocol}//${link.host}${link.pathname}${link.search}${link.hash}`;
  }

  /**
   * 页面隐藏前检查是否还有未发送的日志
   * 
   * @memberof Lajax
   */
  _storageUnsendData() {
    const originOnHide = this.app.onHide;
    this.app.onHide = () => {
      if (this.queue.length) {
        // 存入微信缓存，下次进入页面时会自动发送一次日志
        wx.setStorageSync('lajax', JSON.stringify(this.queue));
      }
      originOnHide();
    };

    const originOnShow = this.app.onShow;
    this.app.onShow = () => {
      this._loadFromStorage();
      originOnShow();
    };
  }

  /**
   * 发送日志请求
   * 
   * @memberof Lajax
   */
  _send() {

    const logCount = this.queue.length;
    if (logCount) {
      // 如果存在 this.requestTask，说明上一次的请求还没有结束，就又准备发送新的请求了，则直接终止上次请求
      if (this.requestTask) {
        this.requestTask.abort();
      }
      var that = this;
      //20191226 houzb 去除掉此处的日志上报
       // 日志发送成功，从队列中去除已发送的
       that.queue.splice(0, logCount);

       // 重置请求出错次数
       that.errorReq = 0;

       // 显示日志发送成功
       if (that.console && that.printLogFlg) {
         if (that.stylize) {
           // that.console.log(`%c[${that._getTimeString(null)}] - 已发送 ${logCount} 条日志，剩余 ${that.queue.length} 条日志待发送`, `color: ${Lajax.colorEnum.sendSuccess}`);
         } else {
           that.console.log(`${logCount}条日志发送成功！`);
         }
       }
       return;

      this.requestTask = this.app.executeRequest(function(header) {

        wx.request({
          url: that.url,
          method: 'POST',
          // data: {
          //   msg: encodeURIComponent(JSON.stringify(that.queue)),
          //   phoneOs: that.systemInfo.system,//手机系统信息,最长245
          //   phoneFactory: that.systemInfo.brand,//手机厂商,最长245
          //   phoneType: that.systemInfo.model,//手机型号,最长245
          //   clientVer: that.systemInfo.version,
          //   sdkVer: that.systemInfo.SDKVersion,
          //   miniVer: that.miniVer
          // },
          data: {
            log: encodeURIComponent(JSON.stringify(that.queue)),
          },
          header: header,
          success: () => {
            // 日志发送成功，从队列中去除已发送的
            that.queue.splice(0, logCount);

            // 重置请求出错次数
            that.errorReq = 0;

            // 显示日志发送成功
            if (that.console && that.printLogFlg) {
              if (that.stylize) {
                // that.console.log(`%c[${that._getTimeString(null)}] - 已发送 ${logCount} 条日志，剩余 ${that.queue.length} 条日志待发送`, `color: ${Lajax.colorEnum.sendSuccess}`);
              } else {
                that.console.log(`${logCount}条日志发送成功！`);
              }
            }

          },
          fail: e => {
            // 排除掉abort的错误
            if (e.errMsg !== 'request:fail abort') {
              // that._printConsole(null, Lajax.levelEnum.error, `发送日志请求失败！配置的接口地址：${that.url} 错误描述：${e.errMsg}`);
              that._checkErrorReq();
            }
          },
          complete: () => {
            that.xhr = null;

            if (checkTimeout != null) {
              clearTimeout(checkTimeout);
              checkTimeout = null;
            }
            
            setTimeout(function () {
              that._checkSendLog();
            }, that.interval);
          }
        });

      });
    }
  }

  /**
   * 检查请求出错次数
   * 
   * @memberof Lajax
   */
  _checkErrorReq() {
    // 将出错次数 +1
    this.errorReq++;

    // 超过最大次数则认为服务器不可用，停止定时器
    if (this.errorReq >= this.maxErrorReq) {
      clearInterval(this.timer);
      // this._printConsole(null, Lajax.levelEnum.warn, `发送日志请求的连续失败次数过多，已停止发送日志。请检查日志接口 ${this.url} 是否正常！`);
    }
  }

  /**
   * 获取时间字符串
   * 
   * @param {Date} time - 记录时间 
   * @returns 
   * @memberof Lajax
   */
  _getTimeString(time) {
    const now = (time === null ? new Date() : time);

    // 时
    let hour = String(now.getHours());
    if (hour.length === 1) {
      hour = `0${hour}`;
    }

    // 分
    let minute = String(now.getMinutes());
    if (minute.length === 1) {
      minute = `0${minute}`;
    }

    // 秒
    let second = String(now.getSeconds());
    if (second.length === 1) {
      second = `0${second}`;
    }

    // 毫秒
    let millisecond = String(now.getMilliseconds());
    if (millisecond.length === 1) {
      millisecond = `00${millisecond}`;
    } else if (millisecond.length === 2) {
      millisecond = `0${millisecond}`;
    }

    return `${hour}:${minute}:${second}.${millisecond}`;
  }

  /**
   * 获取日期时间字符串
   * 
   * @param {Date} time - 记录时间
   * @returns 
   * @memberof Lajax
   */
  _getDateTimeString(time) {
    const now = (time === null ? new Date() : time);

    // 年
    const year = String(now.getFullYear());

    // 月
    let month = String(now.getMonth() + 1);
    if (month.length === 1) {
      month = `0${month}`;
    }

    // 日
    let day = String(now.getDate());
    if (day.length === 1) {
      day = `0${day}`;
    }

    return `${year}-${month}-${day} ${this._getTimeString(now)}`;
  }

  /**
   * 调用系统 console 打印日志
   * 
   * @param {any} time 
   * @param {any} level 
   * @param {any} args 
   * @memberof Lajax
   */
  _printConsole(time, level, ...args) {
    if (this.console && this.printLogFlg) {

      if ((level == Lajax.levelEnum.ble_log ||
          level == Lajax.levelEnum.ble_error ||
          level == Lajax.levelEnum.ble_warn) &&
        this.printBleLog == false) {
        return;
      }

      if ((level == Lajax.levelEnum.log ||
          level == Lajax.levelEnum.error ||
          level == Lajax.levelEnum.info ||
          level == Lajax.levelEnum.warn) &&
        this.printAppLog == false) {
        return;
      } 

      // args.unshift(`{${this.app.globalData.reqId}}`);
      var srclevel = level;
      if (srclevel == Lajax.levelEnum.ble_log) {
        srclevel = Lajax.levelEnum.log;
      } else if (srclevel == Lajax.levelEnum.ble_warn) {
        srclevel = Lajax.levelEnum.warn;
      } else if (srclevel == Lajax.levelEnum.ble_error) {
        srclevel = Lajax.levelEnum.error;
      }

      if (this.stylize) {
        if (srclevel == Lajax.levelEnum.ble_log || srclevel == Lajax.levelEnum.log) {
          this.console[srclevel](`%c[${this.sort + 1}][${this._leveStr(level.toUpperCase())}][${this._getDateTimeString(time)}]`, `color: ${Lajax.colorEnum[level]}`, ...args);
        } else {
          this.console[srclevel](`[${this.sort + 1}][${this._leveStr(level.toUpperCase())}][${this._getDateTimeString(time)}]`, ...args);
        }
      } else {
        this.console[srclevel](...args);
      }
    }
  }

  _resetConSort() {
    this.conSort = 0;
  }

  _leveStr(level) {

    if (level == 'LOG') {
      return '日志';
    }
    if (level == 'INFO') {
      return '信息';
    }
    if (level == 'WARN') {
      return '警告';
    }
    if (level == 'ERROR') {
      return '错误';
    }

    if (level == 'BLE_LOG') {
      return '蓝牙日志';
    }
    if (level == 'BLE_ERROR') {
      return '蓝牙错误';
    }
    if (level == 'BLE_WARN') {
      return '蓝牙警告';
    }

    if (level == 'ERROR') {
      return '错误';
    }

    return level
  }

  /**
   * 将日志添加到队列中
   * 
   * @param {any} time 
   * @param {any} level 
   * @param {any} args 
   * @memberof Lajax
   */
  _pushToQueue(time, level, ...args) {

    // args.unshift(`{${this.app.globalData.reqId}}`);

    let url = '';
    const pages = getCurrentPages();
    if (pages && pages.length > 0) {
      const page = pages[pages.length - 1];
      if (page.route) {
        url = page.route;
      }
    }

    var logType = 'APP';
    var isAppLog = true;

    if (level == Lajax.levelEnum.ble_log ||
      level == Lajax.levelEnum.ble_error ||
      level == Lajax.levelEnum.ble_warn) {
      logType = 'BLE';
      isAppLog = false;
    }

    this.queue.push({
      'type': logType,
      reqid: this.app.globalData.reqId,
      sort: ++this.sort,
      appSort: isAppLog ? ++this.appSort : 0,
      bleSort: isAppLog ? 0 : ++this.bleSort,
      conSort: isAppLog ? 0 : ++this.conSort,
      conReqid: this.connectInfo.conReqid,
      conBleMac: this.connectInfo.conBleMac,
      conUid: wx.getStorageSync('user_id') ? wx.getStorageSync('user_id') : null,
      conRFID: isAppLog == false && this.conRFID != null ? this.conRFID : null,
      messages: JSON.stringify(args),
      time: this._getDateTimeString(time),
      level,
      url,
      extra: _EXTRADATA,
      phoneOs: this.systemInfo.system, //手机系统信息,最长245
      phoneFactory: this.systemInfo.brand, //手机厂商,最长245
      phoneType: this.systemInfo.model, //手机型号,最长245
      clientVer: this.systemInfo.version,
      sdkVer: this.systemInfo.SDKVersion,
      miniVer: this.miniVer,
    });

    if (this.queue.length > 3000) {
      this.queue.splice(0, 1);
    }
  }

  _test(message) {
    alert(message);
  }

  _setConnectInfo(info) {
    this.connectInfo = info;
  }

  _resetConnectInfo() {
    this.connectInfo = {
      conReqid: 0,
      conBleMac: null,
    };
  }
  
  /**
   * 将日志打印到控制台并添加到队列
   * 
   * @param {Date} time - 记录时间
   * @param {Lajax.levelEnum} level - 日志级别
   * @param {any} args - 日志内容
   * @memberof Lajax
   */
  _log(time, level, ...args) {

    var str = JSON.stringify(args);

    if (str.indexOf('request success') != -1 || str.indexOf('request begin') != -1) {
      return;
    }

    // 调用系统 console 打印日志
    this._printConsole(time, level, ...args);

    // 将日志添加到队列中
    this._pushToQueue(time, level, ...args);

    // if (level == Lajax.levelEnum.ble_log ||
    //   level == Lajax.levelEnum.ble_error ||
    //   level == Lajax.levelEnum.ble_warn) {

    //   // 将日志添加到队列中
    //   this._pushToQueue(time, level, ...args);

    // }
  }

  /**
   * 重写 console
   * 
   * @memberof Lajax
   */
  _overwriteConsole() {
    if (console) {

      // 记录一条信息日志
      console.info = (...args) => {
        this._log(null, Lajax.levelEnum.info, ...args);
      };

      // 记录一条普通日志
      console.log = (...args) => {
        this._log(null, Lajax.levelEnum.log, ...args);
      };

      // 记录一条警告日志
      console.warn = (...args) => {
        this._log(null, Lajax.levelEnum.warn, ...args);
      };

      // 记录一条错误日志
      console.error = (...args) => {
        this._log(null, Lajax.levelEnum.error, ...args);
      };



      // // 记录一条普通日志
      // console.bleLog = (...args) => {
      //   this._log(null, Lajax.levelEnum.ble_log, ...args);
      // };

      // // 记录一条警告日志
      // console.bleWarn = (...args) => {
      //   this._log(null, Lajax.levelEnum.ble_warn, ...args);
      // };

      // // 记录一条错误日志
      // console.bleError = (...args) => {
      //   this._log(null, Lajax.levelEnum.ble_error, ...args);
      // };

    }
  }
  /* eslint-enable no-console, no-bitwise*/
}

/**
 * 日志级别枚举
 */
Lajax.levelEnum = {

  log: 'log',

  /**
   * 信息
   */
  info: 'info',

  /**
   * 警告
   */
  warn: 'warn',

  /**
   * 错误
   */
  error: 'error',

  ble_log: 'ble_log',

  ble_error: 'ble_error',

  ble_warn: 'ble_warn',

};

/**
 * 日志颜色枚举
 */
Lajax.colorEnum = {

  ble_log: '#0000FF',

  ble_error: '#0000FF',

  ble_warn: 'orange',

  log: '#333333',

  /**
   * 信息日志颜色，默认宝蓝色
   */
  info: 'DodgerBlue',

  /**
   * 警告日志颜色，默认橘黄色
   */
  warn: 'orange',

  /**
   * 错误日志颜色，默认红色
   */
  error: 'red',

  /**
   * ajax分组颜色，默认紫色
   */
  ajaxGroup: '#800080',

  /**
   * 日志发送成功颜色，默认绿色
   */
  sendSuccess: 'green',

  /**
   * 描述文字颜色，默认粉红色
   */
  desc: '#d30775',
};

module.exports = {
  init: config => new Lajax(config),
  setExtraData: function(data) {
    _EXTRADATA = data;
  },
  sendLog: function() {
    lajaxObj._send()
  },
  setConnectInfo: function(info) {
    lajaxObj._setConnectInfo(info)
  },
  resetConSort: function() {
    lajaxObj._resetConSort();
  },
  reInit: function() {
    lajaxObj._init();
  },
  setConRFID: function(rfid) {
    lajaxObj._setConRFID(rfid);
  },
};